#include "stdafx.h"
#include "DXDevice.h"
#include "GameEngine.h"
#include "StringUtil.h"
#include "DirectInput.h"
#include "Camera.h"
#include "StringUtil.h"

#include <boost/foreach.hpp>


DXDevice* DXDevice::m_DxDevicePtr = NULL;

DXDevice::DXDevice(): m_D3D10DevicePtr(0), m_SwapChainPtr(0), m_driverType(D3D10_DRIVER_TYPE_NULL),
			m_RenderTargetViewPtr(0), m_DepthStencilPtr(0), m_DepthStencilViewPtr(0), 
			m_FontSpritePtr(0), m_RSPtr(0), m_CameraPtr(0), m_FontColor(0,0,0,1),m_FPSCounter(0),m_FPSTotal(0),m_FPS(0), 
			m_AvgFPS(0)
{

}

DXDevice::~DXDevice()
{
	delete m_CameraPtr;
	delete DirectInput::GetSingleton();
}

DXDevice* DXDevice::GetSingleton()
{
	if(m_DxDevicePtr == NULL)
		m_DxDevicePtr = new DXDevice();
	return m_DxDevicePtr;
}

HRESULT DXDevice::Init()
{
	HRESULT hr = S_OK;

	HR(InitSwapChain());
	HR(InitRenderTargetView());
	HR(InitDepthStencil());
	HR(InitFont());
	HR(InitBlend());

	m_D3D10DevicePtr->OMSetRenderTargets( 1, &m_RenderTargetViewPtr, m_DepthStencilViewPtr );
	SetViewPort();

	if(FAILED(hr))
		return hr;

	// Start DirectInput
	DirectInput::GetSingleton();

	// Make Light
	m_Light.pos		 = D3DXVECTOR3(0, 200, -500);
	m_Light.dir      = D3DXVECTOR3(0.57735f, -0.57735f, 0.57735f);
	m_Light.ambient  = D3DXVECTOR4(0.4f, 0.4f, 0.4f, 1.0f);
	m_Light.diffuse  = D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f);
	m_Light.spec	 = D3DXVECTOR4(0.0f, 0.0f, 0.0f, 1.0f);
	m_Light.spotPower = 100;
	m_Light.range	 = 800;
	return S_OK;

}

#pragma region Inits
HRESULT DXDevice::InitSwapChain()
{
	HRESULT hr = S_OK;

	UINT createDeviceFlags = 0;
	#ifdef _DEBUG
		createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
	#endif

	D3D10_DRIVER_TYPE driverTypes[] =
	{
		D3D10_DRIVER_TYPE_HARDWARE,
		D3D10_DRIVER_TYPE_REFERENCE,
	};

	UINT numDriverTypes = sizeof(driverTypes) / sizeof(driverTypes[0]);

	// Create Swap Chain
	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory( &sd, sizeof( sd ) );

	sd.BufferCount = 1;
	sd.BufferDesc.Width = GAME_ENGINE->GetWidth();
	sd.BufferDesc.Height = GAME_ENGINE->GetHeight();
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;

	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	
	sd.OutputWindow = GameEngine::GetSingleton()->GetWindow();
	
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	
	sd.Windowed = TRUE;
	sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	

	// Look for 'NVIDIA PerfHUD' adapter
	// If it is present, override default settings
	IDXGIFactory *pDXGIFactory;
	HRESULT hRes;
	hRes = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&pDXGIFactory);
	// Search for a PerfHUD adapter.
	UINT nAdapter = 0;
	IDXGIAdapter* adapter = NULL;
	IDXGIAdapter* selectedAdapter = NULL;
	D3D10_DRIVER_TYPE driverType = D3D10_DRIVER_TYPE_HARDWARE;

#ifdef _DEBUG

	while (pDXGIFactory->EnumAdapters(nAdapter, &adapter) != DXGI_ERROR_NOT_FOUND)
	{
		if (adapter)
		{
			DXGI_ADAPTER_DESC adaptDesc;
			if (SUCCEEDED(adapter->GetDesc(&adaptDesc)))
			{
				const bool isPerfHUD = wcscmp(adaptDesc.Description, _T("NVIDIA PerfHUD")) == 0;
				// Select the first adapter in normal circumstances or the PerfHUD one if it exists.
					if(nAdapter == 0 || isPerfHUD)
						selectedAdapter = adapter;
				if(isPerfHUD)
					driverType = D3D10_DRIVER_TYPE_REFERENCE;
			}
		}
		++nAdapter;
	}
#endif

	m_driverType = driverType;
	hr = D3D10CreateDeviceAndSwapChain( selectedAdapter, m_driverType, NULL, createDeviceFlags,
		D3D10_SDK_VERSION, &sd, &m_SwapChainPtr, &m_D3D10DevicePtr );

	return hr;
}

HRESULT DXDevice::InitRenderTargetView()
{
	HRESULT hr = S_OK;

	// Create Render Target View
	ID3D10Texture2D* backBufferPtr;
	hr = m_SwapChainPtr->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)( &backBufferPtr ));
	if(FAILED(hr))
		return hr;

	hr = m_D3D10DevicePtr->CreateRenderTargetView( backBufferPtr, NULL, &m_RenderTargetViewPtr );
	backBufferPtr->Release();
	if( FAILED( hr ) )
		return hr;

	return hr;
}
HRESULT DXDevice::InitDepthStencil()
{
	HRESULT hr = S_OK;

	// Create depth stencil texture
	D3D10_TEXTURE2D_DESC descDepth;
	descDepth.Width = GAME_ENGINE->GetWidth();
	descDepth.Height = GAME_ENGINE->GetHeight();
	
	descDepth.MipLevels = 1;
	descDepth.ArraySize = 1;
	descDepth.Format = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;

	descDepth.SampleDesc.Count = 1;
	descDepth.SampleDesc.Quality = 0;
	
	descDepth.Usage = D3D10_USAGE_DEFAULT;
	descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
	descDepth.CPUAccessFlags = 0;
	descDepth.MiscFlags = 0;
	
	hr = m_D3D10DevicePtr->CreateTexture2D( &descDepth, NULL, &m_DepthStencilPtr );
	if( FAILED(hr) )
		return hr;

	// Create the depth stencil view
	D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
	descDSV.Format = descDepth.Format;
	descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
	descDSV.Texture2D.MipSlice = 0;
	hr = m_D3D10DevicePtr->CreateDepthStencilView( m_DepthStencilPtr, &descDSV, &m_DepthStencilViewPtr );
	if( FAILED(hr) )
		return hr;
	return hr;
}

HRESULT DXDevice::InitFont()
{
	HRESULT hr = S_OK;

	ID3DX10Font* tempFont;
	hr = D3DX10CreateFont(m_D3D10DevicePtr, 15, 0, FW_BOLD, 1, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, _T("Arial"), &tempFont);
	if(FAILED(hr)) return hr;
	m_Font.insert(make_pair(_T("Arial_15"), tempFont));
	m_ActiveFont = tempFont;

	hr = D3DX10CreateSprite(m_D3D10DevicePtr, 512, &m_FontSpritePtr);
	if(FAILED(hr)) return hr;

	return S_OK;
}

void DXDevice::SetViewPort()
{
	// Setup the viewport
	D3D10_VIEWPORT vp;
	vp.Width = GAME_ENGINE->GetWidth();
	vp.Height = GAME_ENGINE->GetHeight();
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	m_D3D10DevicePtr->RSSetViewports( 1, &vp );

	// Create Viewmatrix
	D3DXMatrixOrthoOffCenterLH(&m_SpriteViewProj, (float)vp.TopLeftX, 
		(float)vp.Width, (float)vp.TopLeftY, (float)vp.Height, 	0.0f,	1.0f);
}

HRESULT DXDevice::InitBlend()
{
	D3D10_BLEND_DESC blendDesc = {0};
	blendDesc.AlphaToCoverageEnable = false;
	blendDesc.BlendEnable[0] = true;
	blendDesc.SrcBlend = D3D10_BLEND_SRC_ALPHA;
	blendDesc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA;
	blendDesc.BlendOp = D3D10_BLEND_OP_ADD;
	blendDesc.SrcBlendAlpha = D3D10_BLEND_ONE;
	blendDesc.DestBlendAlpha = D3D10_BLEND_ZERO;
	blendDesc.BlendOpAlpha = D3D10_BLEND_OP_ADD;
	blendDesc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;

	return m_D3D10DevicePtr->CreateBlendState(&blendDesc, &m_BlendState);
}
#pragma endregion

D3DXMATRIX DXDevice::GetSpriteViewProj() const
{
	return m_SpriteViewProj;
}



void DXDevice::Tick(float dTime)
{
	m_CameraPtr->Tick(dTime);
}

void DXDevice::Render()
{
	// Just clear the backbuffer
    float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
    m_D3D10DevicePtr->ClearRenderTargetView( m_RenderTargetViewPtr, ClearColor );
	m_D3D10DevicePtr->ClearDepthStencilView( m_DepthStencilViewPtr, D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, 1.0f, 0 );
	
	// Restore default states, input layout and primitive topology 
	// because mFont->DrawText changes them.  Note that we can 
	// restore the default states by passing null.
	m_D3D10DevicePtr->OMSetDepthStencilState(0, 0);
	float blendFactor[] = {0.0f, 0.0f, 0.0f, 0.0f};
	m_D3D10DevicePtr->OMSetBlendState(0, blendFactor, 0xffffffff);

	GAME_ENGINE->GetGame()->Paint();
    m_SwapChainPtr->Present( 0, 0 );
	ShowFps();
}

void DXDevice::Cleanup()
{
	LOG(_T("Gemiddelde FPS: ") + StringUtil::ToString(m_AvgFPS));
	if( m_D3D10DevicePtr ) m_D3D10DevicePtr->ClearState();
    if( m_RenderTargetViewPtr ) m_RenderTargetViewPtr->Release();
    if( m_SwapChainPtr ) m_SwapChainPtr->Release();
	pair<tstring, ID3DX10Font*> font;
	BOOST_FOREACH(font, m_Font) font.second->Release();
	if( m_FontSpritePtr ) m_FontSpritePtr->Release();
    if( m_D3D10DevicePtr ) m_D3D10DevicePtr->Release();
	if( m_DepthStencilPtr ) m_DepthStencilPtr->Release();
	if( m_DepthStencilViewPtr ) m_DepthStencilViewPtr->Release();

	delete m_DxDevicePtr;
	m_DxDevicePtr = NULL;
}

D3DXMATRIX DXDevice::GetViewProj() const
{
	return (m_CameraPtr->GetView() * m_CameraPtr->GetProj());
}

void DXDevice::DrawText(const tstring& text, int x, int y) const
{
	DrawText(text, x, y, 800, 600, DT_LEFT);
}

void DXDevice::DrawText(const tstring& text, int x, int y, int width, int height) const
{
	DrawText(text, x, y, width, height, DT_LEFT);
}

void DXDevice::DrawText(const tstring& text, int x, int y, int width, int height, int align) const
{
	m_FontSpritePtr->Begin(D3DX10_SPRITE_SORT_TEXTURE);
	RECT r;
	r.left = x;
	r.top = y;
	r.bottom = y + height;
	r.right = x + width;

	m_ActiveFont->DrawText(m_FontSpritePtr, text.c_str(), -1, &r, align ,m_FontColor);

	m_FontSpritePtr->End();
	//DXDEVICE->GetDevice()->OMSetDepthStencilState(0,0);
	//this->DisableBlend();
}

ID3D10Device* DXDevice::GetDevice() const
{
	return m_D3D10DevicePtr;
}

void DXDevice::SetupCamera(D3DXVECTOR3 pos = D3DXVECTOR3(0, 5, -7), D3DXVECTOR3 target = D3DXVECTOR3(0, 1, 0))
{
	D3DXMatrixIdentity(&m_World);
	m_CameraPtr = new Camera(pos, target);
	m_CameraPtr->SetSpeed(5);
}

void DXDevice::SetCamera(const D3DXVECTOR3& pos, const D3DXVECTOR3& lookat)
{
	m_CameraPtr->SetCamera(pos, lookat);
}

void DXDevice::GetMatrixen(D3DXMATRIX &world, D3DXMATRIX &view, D3DXMATRIX &proj)
{
	world = m_World;
	view = m_CameraPtr->GetView();
	proj = m_CameraPtr->GetProj();
}

void DXDevice::GetMatrixen(D3DXMATRIX &view, D3DXMATRIX &proj)
{
	view = m_CameraPtr->GetView();
	proj = m_CameraPtr->GetProj();
}


const D3DXCOLOR& DXDevice::GetColor() const
{
	return m_FontColor;
}

void DXDevice::SetColor(const D3DXCOLOR& kleur)
{
	m_FontColor = kleur;
}

void DXDevice::ShowFps()
{
	LONGLONG deltaTime = 0;
	LARGE_INTEGER lpFrequency;
	QueryPerformanceFrequency(&lpFrequency);
	QueryPerformanceCounter( &m_CurrentTime );
	deltaTime  = m_CurrentTime.QuadPart - m_PrevTime.QuadPart;
	m_PrevTime = m_CurrentTime;

	//draw fps
	if(deltaTime!=0){
		int fps = (int)(lpFrequency.QuadPart/deltaTime);//100
		m_FPSTotal+=fps;
		m_FPSCounter++;
		if(m_FPSCounter>=m_FPS/5)
		{
			m_FPS = m_FPSTotal/m_FPSCounter;
			m_FPSCounter=0;
			m_FPSTotal=0;
		}
		if(m_FPS!=0)
		{
			m_AvgFPS = (m_AvgFPS + m_FPS) / 2;
			tstringstream strstr;
			strstr <<  m_FPS << _T(" FPS");
			SetWindowText(GAME_ENGINE->GetWindow(), strstr.str().c_str());
		}
	}
}

Camera* DXDevice::GetCamera()
{
	return m_CameraPtr;
}

void DXDevice::SetFont(const tstring &type, int size, bool bold, bool cursief, bool underline)
{
	HRESULT hr = S_OK;

	map<tstring, ID3DX10Font*>::iterator index = m_Font.find(type + _T("_") + StringUtil::ToString(size));
	if(index != m_Font.end())
		m_ActiveFont = index->second;
	else
	{
		ID3DX10Font* tempFont;
		HR(D3DX10CreateFont(m_D3D10DevicePtr, size, 0, FW_BOLD, 1, cursief, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH || FF_DONTCARE, type.c_str(), &tempFont));
		m_Font.insert(make_pair(type + _T("_") + StringUtil::ToString(size), tempFont));
		m_ActiveFont = tempFont;
	}
}

void DXDevice::SetDefaultFont()
{
	SetFont(_T("Arial"), 15, false, false, false);
}
void DXDevice::OnResize()
{
	m_CameraPtr->SetLens((float)D3DX_PI/4, (float)GAME_ENGINE->GetWidth() / (float)GAME_ENGINE->GetHeight(), 0.1f, 5300.0f);
	SetViewPort();
}
void DXDevice::EnableBlend()
{
	float blendFactors[] = {0.0f, 0.0f, 0.0f, 0.0f};
	m_D3D10DevicePtr->OMSetBlendState(m_BlendState, blendFactors, 0xffffffff);
}
void DXDevice::DisableBlend() const
{
	float blendFactors[] = {0.0f, 0.0f, 0.0f, 0.0f};
	m_D3D10DevicePtr->OMSetBlendState(0, 0, 0xffffffff);
}

void DXDevice::ResetRenderTargets()
{
	m_D3D10DevicePtr->OMSetRenderTargets(1, &m_RenderTargetViewPtr, m_DepthStencilViewPtr);

	SetViewPort();
}
Light& DXDevice::GetLight()
{
	return m_Light;
}

void DXDevice::ClearDepth()
{
	m_D3D10DevicePtr->ClearDepthStencilView( m_DepthStencilViewPtr, D3D10_CLEAR_DEPTH, 1.0f, 0 );
}

void DXDevice::ClearStencil()
{
	m_D3D10DevicePtr->ClearDepthStencilView( m_DepthStencilViewPtr, D3D10_CLEAR_STENCIL, 1.0f, 0 );
}